﻿using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Xml;
using GeekShop.Properties;
using System;

namespace GeekShop
{
    public class Gradient
    {
        #region Fields
        private string name;
        private int maxValue = 16777215;
        private SortedDictionary<int, Marker> markers;
        #endregion

        #region Static Properties
        public static Gradient Empty
        {
            get
            {
                SortedDictionary<int, Marker> markers = new SortedDictionary<int, Marker>();
                markers.Add(0, new Marker(Color.Black));
                markers.Add(16777215, new Marker(Color.Black));
                return new Gradient("Empty Filter", markers);
            }
        }
        public static Gradient TransparentFade
        {
            get
            {
                SortedDictionary<int, Marker> markers = new SortedDictionary<int, Marker>();
                markers.Add(0, new Marker(Color.Black));
                markers.Add(16777215, new Marker(Color.FromArgb(0, Color.White)));
                return new Gradient("Transparent Fade", markers);
            }
        }
        public static Gradient LightSpectrum
        {
            get
            {
                SortedDictionary<int, Marker> markers = new SortedDictionary<int, Marker>();
                markers.Add(0, new Marker(Color.Black));
                markers.Add(16777215, new Marker(Color.Black));

                for (int i = 1; i < Resources.spectrum.Width; i++)
                {
                    int index = (int)Math.Round((double)i / Resources.spectrum.Width * 16777215);
                    Color color = Resources.spectrum.GetPixel(i, 0);
                    markers.Add(
                        index,
                        new Marker(color)
                    );
                }

                return new Gradient("Light Spectrum", markers);
            }
        }
        #endregion

        #region Properties
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public SortedDictionary<int, Marker> Markers
        {
            get { return markers; }
            set { markers = value; }
        }
        #endregion

        #region Constructors
        public Gradient() : this("New Filter") { }
        public Gradient(string name)
        {
            this.markers = new SortedDictionary<int, Marker>();
            this.markers.Add(0, new Marker(Color.Black));
            this.markers.Add(16777215, new Marker(Color.White));
            this.name = name;
        }
        public Gradient(string name, SortedDictionary<int, Marker> markers)
        {
            this.markers = markers;
            this.name = name;
        }
        #endregion

        #region Indexer
        public Color this[int index]
        {
            get { return this.getColor(index); }
        }
        #endregion

        #region Private Methods
        private Color fade(double percentage, Color c1, Color c2)
        {
            Color color = Color.FromArgb(
                    (int)(c2.A * percentage / 100 + c1.A * (100 - percentage) / 100),
                    (int)(c2.R * percentage / 100 + c1.R * (100 - percentage) / 100),
                    (int)(c2.G * percentage / 100 + c1.G * (100 - percentage) / 100),
                    (int)(c2.B * percentage / 100 + c1.B * (100 - percentage) / 100)
                );
            return color;
        }
        #endregion

        #region Public Methods
        public void AddMarker(int index, Marker marker)
        {
            this.markers.Add(index, marker);
        }
        
        /// <param name="percent">a value between 0 and 1</param>
        /// <returns>the estimated color</returns>
        public Color getColor(double percent)
        {
            return this.getColor((int)(percent * this.maxValue));
        }

        /// <param name="index">a value between 0 and 16777215</param>
        /// <returns>the estimated color</returns>
        public Color getColor(int index)
        {
            Marker tempMarker;
            this.markers.TryGetValue(0, out tempMarker);
            Color leftColor = tempMarker.Color;
            
            this.markers.TryGetValue(this.maxValue, out tempMarker);
            Color rightColor = tempMarker.Color;

            if (index < 0)
            {
                return leftColor;
            }
            else if (index >= this.maxValue)
            {
                return rightColor;
            }

            Marker m;
            if (this.Markers.TryGetValue(index, out m))
            {
                return m.Color;
            }

            int leftIndex = 0, rightIndex = 0, distance = 0;
            double percentage = 0;

            SortedDictionary<int, Marker>.Enumerator marker = this.Markers.GetEnumerator();

            while (marker.MoveNext())
            {
                if (marker.Current.Key < index)
                {
                    leftIndex = marker.Current.Key;
                    leftColor = marker.Current.Value.Color;
                }
                else
                {
                    rightIndex = marker.Current.Key;
                    rightColor = marker.Current.Value.Color;
                    break;
                }
            }

            distance = rightIndex - leftIndex;
            percentage = ((double)index - leftIndex) / distance * 100;

            return this.fade(percentage, leftColor, rightColor);
        }
        public void Save()
        {
            if (!Directory.Exists("Filters"))
            {
                Directory.CreateDirectory("Filters");
            }

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.ConformanceLevel = ConformanceLevel.Document;
            settings.Encoding = System.Text.Encoding.UTF8;
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create("Filters\\" + this.name.GetHashCode().ToString("X8") + ".xml", settings))
            {
                SortedDictionary<int, Marker>.Enumerator enumerator = this.markers.GetEnumerator();

                writer.WriteStartElement("Filter");
                writer.WriteElementString("Name", this.name);
                while (enumerator.MoveNext())
                {
                    KeyValuePair<int, Marker> marker = enumerator.Current;
                    Color color = marker.Value.Color;

                    writer.WriteStartElement("Marker");
                    writer.WriteElementString("Index", marker.Key.ToString());
                    writer.WriteElementString("Color", "#" + color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2"));
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
        }
        public Gradient Clone()
        {
            SortedDictionary<int, Marker> markers = new SortedDictionary<int, Marker>();
            SortedDictionary<int, Marker>.Enumerator e = this.markers.GetEnumerator();
            while (e.MoveNext())
            {
                markers.Add(e.Current.Key, e.Current.Value);
            }

            return new Gradient(this.name, markers);
        }
        #endregion

        #region Static Methods
        public static Gradient[] Load()
        {
            if (!Directory.Exists("Filters"))
            {
                return new Gradient[0];
            }

            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ConformanceLevel = ConformanceLevel.Document;
            settings.IgnoreWhitespace = true;
            settings.IgnoreComments = true;

            string[] files = Directory.GetFiles("Filters");
            List<Gradient> filters = new List<Gradient>();

            foreach (string file in files)
            {
                Gradient filter = new Gradient();
                using (XmlReader reader = XmlReader.Create(file, settings))
                {
                    while (reader.Read())
                    {
                        if (reader.IsStartElement() && !reader.IsEmptyElement)
                        {
                            if (reader.Name == "Name")
                            {
                                filter.Name = reader.ReadString();
                                continue;
                            }
                            if (reader.Name == "Marker")
                            {
                                int index = 0;
                                Color color = Color.Black;

                                reader.Read();
                                if (reader.Name == "Index")
                                {
                                    index = int.Parse(reader.ReadString());
                                }

                                reader.Read();
                                if (reader.Name == "Color")
                                {
                                    string hexColor = reader.ReadString().Substring(1);
                                    color = Color.FromArgb(
                                        int.Parse(hexColor.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
                                        int.Parse(hexColor.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
                                        int.Parse(hexColor.Substring(4, 2), System.Globalization.NumberStyles.HexNumber)
                                        );
                                }

                                filter.AddMarker(index, new Marker(color));
                            }
                        }
                    }
                }
                filters.Add(filter);
            }

            return filters.ToArray();
        }
        #endregion
    }
}
